using System;
using System.Collections;
using System.Configuration;
using System.IO;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using System.Xml.Serialization;
using SqlRunner.Events;
using SqlRunner.UserInterfaceStatus;
using SQLRunnerLib.Project;
using SQLRunnerLib.Runners;
using SQLRunnerLib.Services;
using SQLRunnerLib.Util;
using SQLServices.Log;
using SQLServices.Runners;
using SQLServices.Runners.Events;

namespace SqlRunner
{
	/// <summary>
	/// Controller for the main application form.
	/// </summary>
	public class FormMainController
	{
		#region Private Instances

//		private static FormMainController singletonInstance;
		private FormMain formInstance;
		private ArrayList folderList;
		private SQLProject _project;

		/// <summary>
		/// Provides a way to update the UI when the Runner is executing
		/// SQL script files
		/// </summary>
		private AddLogEventHandler addLogEvent;

		/// <summary>
		/// Indicates that a script was executed
		/// </summary>
		private SuccessScriptHandler scriptRunEvent;

		/// <summary>
		/// Indicates that an error took place when a script was run
		/// </summary>
		private ErrorScriptHandler scriptErrorEvent;

		/// <summary>
		/// Indicates that a new message was created by the runner to be
		/// displayed by the UI
		/// </summary>
		private AddMessageHandler addMessageEvent;

		#endregion
		#region Properties

		public ArrayList FolderList {
			get { return folderList; }
			set { folderList = value; }
		}

		public SQLProject Project {
			get { return _project; }
			set { _project = value; }
		}

		#endregion
		#region Constructors

		public FormMainController() 
		{
			formInstance = new FormMain(this);
			addMessageEvent = new AddMessageHandler(formInstance.setComment);
			scriptErrorEvent = new ErrorScriptHandler(formInstance.increaseErrorCounter);
			scriptRunEvent = new SuccessScriptHandler(formInstance.increaseTotalCounter);
			addLogEvent = new AddLogEventHandler(formInstance.addLog);
		}

		#endregion
		#region Public Methods

		// Use it to save the last selected path
		public static string LastPath = "";
		private Thread _scripts;

		public FormMain FormInstance {
			get { return formInstance; }
			set { formInstance = value; }
		}

		public int TotalFiles() {
			return SQLProject.FilesInProject(Project).Count;
		}

		public void RefreshProject() {
			if (_project != null) 
			{
				formInstance.clearFolders();
				foreach (Folder folder in _project.Folders) 
				{
					formInstance.addFolder(folder.Path, folder.IncludeAllSubFolders);
				}
			}
		}
		public void runScripts() {
			IRunner runner = ServiceContainer.CreateRunner(Project);
			HookEvents(runner);
			ServiceContainer.Logger.LogMessage("frmMainHelper - runScripts method was called");
			// Start a new thread to run the scripts so the UI can be refreshed
			_scripts = new Thread(new ThreadStart(runner.RunScriptsAsync));
			_scripts.Name = "Runner";
			_scripts.IsBackground = true;
			_scripts.Start();
		}

		public RunnerProperties ReadProperties(FileSystemInfo file) {
			XmlSerializer aSerializer = new XmlSerializer(typeof (RunnerProperties));
			FileStream aReader = new FileStream(file.Name, FileMode.Open);
			RunnerProperties aProperties = (RunnerProperties) aSerializer.Deserialize(aReader);
			aReader.Close();
			return aProperties;
		}

		public void WriteProperties(RunnerSettings runnerSettings, string serverName,
		                            string databaseName, string userId) {
			RunnerProperties runnerProperties = runnerSettings.FindSetPropertiesByKeyField(serverName);
			if (runnerProperties == null) {
				runnerProperties = new RunnerProperties();
				ArrayList aList = new ArrayList(4);
				RunnerProperty property = new RunnerProperty("servername", serverName);
				aList.Add(property);
				property = new RunnerProperty("databasename");
				aList.Add(property);
				property = new RunnerProperty("uid");
				aList.Add(property);
				property = new RunnerProperty("path");
				aList.Add(property);
				runnerProperties.Items = (RunnerProperty[]) aList.ToArray(typeof (RunnerProperty));
				runnerSettings.Properties.Add(runnerProperties);
			}
			runnerProperties.FindProperty("databasename").value = databaseName;
			runnerProperties.FindProperty("uid").value = userId;
			runnerProperties.FindProperty("path").value = LastPath;

			runnerSettings.key = "servername";
			runnerSettings.defaultProperties = serverName;
			runnerSettings.Serialize(ConfigurationSettings.AppSettings["RunnerPropertiesFile"]);
		                            }


		public ArrayList GetFolderList(CheckedListBox folders, IWin32Window sender) {
			ArrayList folderlist = new ArrayList();
			foreach (object item in folders.Items) {
				// Get list dir
				DirectoryInfo aDir = new DirectoryInfo(item.ToString());

				// Check if exist
				if (aDir.Exists) {
					ScriptFolder folder = new ScriptFolder(aDir, folders.CheckedItems.Contains(item));
					folderlist.Add(folder);
				}
				else {
					MessageBox.Show(sender, String.Format("The {0} does not exists", item.ToString()));
				}
			}
			return folderlist;
		}

		/// <summary>
		/// Refresh the list of folders from the GUI, it keeps the extensions and 
		/// full path if exist.
		/// </summary>
		/// <param name="folders">ListBox control in the GUI</param>
		/// <param name="sender"></param>
		/// <returns>SQLProject with the new list of folders</returns>
		public SQLProject RefreshSQLProject(CheckedListBox folders, IWin32Window sender) {
			if (_project == null){
				_project = new SQLProject();
				_project.Extensions.Add("sql");
			}
			_project.Folders = new ArrayList(folders.Items.Count);
			foreach (object item in folders.Items) {
				// Get list dir
				DirectoryInfo aDir = new DirectoryInfo(item.ToString());

				// Check if exist
				if (aDir.Exists) {
					Folder folder = new Folder();
					folder.Path = aDir.FullName;
					folder.IncludeAllFiles = true;
					folder.IncludeAllSubFolders = folders.CheckedItems.Contains(item);
					if (folder.IncludeAllSubFolders)
						AddSubfolders(aDir, folder);
					_project.Folders.Add(folder);
				}
				else {
					MessageBox.Show(sender, String.Format("The {0} does not exists", item.ToString()));
				}
			}
			return _project;
		}

		public void OpenSQLProject(string fileName, IWin32Window sender) {
			_project = SQLProject.Deserialize(fileName);
			FormInstance.clearFolders();
			foreach (Folder folder in _project.Folders) {
				FormInstance.addFolder(folder.Path, folder.IncludeAllSubFolders);
			}
		}

		public TreeNode GetParentNode(TreeNodeCollection nodes, IPathItem item) {
			// check if node exists
			TreeNode parent = FindNodeByName(nodes, item);
			if (parent == null) {
				parent = nodes.Add(item.Name);
			}
			// check if the next node is a subfolder
			if (item.NextNode != null) {
				return GetParentNode(parent.Nodes, item.NextNode);
			}
			return parent;
		}

		public void ConnectToSql(ComboBox comboBox, string server,
		                         string userName, string password, bool trusted, RunnerProperties properties) {
			ServiceContainer.SQLConnection.OpenConnection(server, userName, password, trusted);
			comboBox.Items.Clear();
			comboBox.Items.AddRange(ServiceContainer.SQLConnection.ListOfDatabases().ToArray());
			FindDefaultDatabase(properties, comboBox);
		}

		public StringBuilder ParseErrorMessage(string[] strPhrases) {
			StringBuilder strBuilder = new StringBuilder();
			char[] rc = {(char) 13, (char) 10};
			for (int i = 0; i < strPhrases.Length; i++) {
				char[] separators = {'[', ']'};
				String[] words = strPhrases[i].Split(separators);
				for (int j = 0; j < words.Length; j++) {
					if (strBuilder.Length > 0 && words[j].Length > 0)
						strBuilder.Append(rc);
					strBuilder.Append(words[j]);
				}
			}
			return strBuilder;
		}

		public void AddFolders(ArrayList folders, CheckedListBox listBox) {
			for (int i = 0; i < folders.Count; i++) {
				string path = ((DirectoryInfo) folders[i]).FullName;
				if (!listBox.Items.Contains(path.ToLower()))
				{
					listBox.Items.Add(path.ToLower());
					FormInstance.FolderStatus = Status.Dirty;
				}
			}
		}

		public void CancelRunner() {
			if (_scripts != null && _scripts.IsAlive) {
				ServiceContainer.Logger.LogMessage(
					String.Format("Script batch execution was cancelled by the user"),
					MsgLevel.Info);
				_scripts.Interrupt();
				FormInstance.ApplicationStatus = Status.Cancel;
			}
		}

		public void AbortRunner() {
			if (_scripts != null && _scripts.IsAlive) {
				ServiceContainer.Logger.LogMessage(
					String.Format("Script batch execution was aborted by the application"),
					MsgLevel.Info);
				_scripts.Abort();
				FormInstance.ApplicationStatus = Status.Cancel;
				FormInstance.FolderStatus = FormInstance.PrevFolderStatus;
			}
		}

		#endregion
		#region Private Methods

		#region Event Methods

		private void HookEvents(IRunner runner) {
			runner.ProgressMsgCreated += new ProgressMsgCreatedEventHandler(OnProgressMsgCreated);
			runner.ScriptExecError += new ScriptExecErrorEventHandler(OnScriptExecError);
			runner.ScriptExecuted += new ScriptExecutedEventHandler(OnScriptExecuted);
			runner.ExecutionFinished += new ExecutionFinishedEventHandler(OnExecutionFinished);
		}

		private void UnHookEvents(IRunner runner) {
			runner.ProgressMsgCreated -= new ProgressMsgCreatedEventHandler(OnProgressMsgCreated);
			runner.ScriptExecError -= new ScriptExecErrorEventHandler(OnScriptExecError);
			runner.ScriptExecuted -= new ScriptExecutedEventHandler(OnScriptExecuted);
			runner.ExecutionFinished -= new ExecutionFinishedEventHandler(OnExecutionFinished);
		}

		private void OnExecutionFinished(object sender, ExecutionFinishedEventArgs e) {
			ServiceContainer.Logger.LogMessage("All scrips were executed", MsgLevel.Info);
			if (FormInstance.ApplicationStatus != Status.Cancel) {
				FormInstance.setComment("All scripts were executed");
			}
			else {
				FormInstance.setComment("Script processor was aborted and not all files were executed");
			}
			FormInstance.ApplicationStatus = Status.DBSelected;
			FormInstance.FolderStatus = FormInstance.PrevFolderStatus;
			UnHookEvents((Runner) sender);
		}

		private void OnScriptExecError(object sender, ScriptExecutionErrorEventArgs e) {
			FormInstance.BeginInvoke(scriptErrorEvent);
			FormInstance.BeginInvoke(scriptRunEvent);
			ServiceContainer.EventLogger.AddError(e.FileName, e.ErrorMsg);
			object[] p = new object[] {ServiceContainer.EventLogger.AddError(e.FileName, e.ErrorMsg)};
			ServiceContainer.Logger.LogMessage(
				String.Format("Script Error - File: [{0}]: {1}", e.FileName, e.ErrorMsg),
				MsgLevel.Info);
			FormInstance.BeginInvoke(addLogEvent, p);
		}

		private void OnProgressMsgCreated(object sender, ProgressMsgCreatedEventArgs e) {
			ServiceContainer.Logger.LogMessage(e.Message, MsgLevel.Info);
			object[] p = new object[] {e.Message, new object[] {}};
			FormInstance.BeginInvoke(addMessageEvent, p);
		}

		private void OnScriptExecuted(object sender, ScriptExecutedEventArgs e) {
			ServiceContainer.Logger.LogMessage(
				String.Format("Runner - script {0} was executed", e.FileName));
			FormInstance.BeginInvoke(scriptRunEvent);
			object[] p = new object[] {ServiceContainer.EventLogger.AddOk(e.FileName)};
			FormInstance.BeginInvoke(addLogEvent, p);
		}

		#endregion

		// Finds node by the node caption
		private TreeNode FindNodeByName(TreeNodeCollection coll, IPathItem path) {
			if (coll == null) return null;
			foreach (TreeNode aNode in coll) {
				// we are looking for subfolder nodes so only
				// interested in nodes with subnodes
				if (aNode.Nodes.Count > 0) {
					// if path and node have same name
					if (aNode.Text.Equals(path.Name)) {
						return aNode;
					}
				}
			}
			return null;
		}

		private void FindDefaultDatabase(RunnerProperties properties, ComboBox cmbDatabases) {
			if (properties != null) {
				RunnerProperty property = properties.FindProperty("databasename");
				string strDB = null;
				if (property != null) strDB = property.value;

				if (strDB != null && cmbDatabases.Items.Contains(strDB)) {
					cmbDatabases.SelectedIndex = cmbDatabases.Items.IndexOf(strDB);
					return;
				}
			}
			if (cmbDatabases.Items.Count > 0) cmbDatabases.SelectedIndex = 0;
		}

		private void AddSubfolders(DirectoryInfo dir, Folder folder) 
		{
			foreach (DirectoryInfo subDir in dir.GetDirectories()) 
			{
				Folder subfolder = new Folder();
				subfolder.Path = subDir.FullName;
				subfolder.IncludeAllFiles = true;
				subfolder.IncludeAllSubFolders = true;
				AddSubfolders(subDir, subfolder);
				folder.Folders.Add(subfolder);
			}
		}

		#endregion
	}
}